import { EntOtpMod, RenderBase } from '@/common/cmdBase/renderBase';
import { widgetManager } from '@/common/managers';
import { lineManager, PointEnum, pointsManager } from '@/common/preview';
import { useDocumentStore } from '@/common/store';
import { nmAppDebugLog } from '@/common/utils';
import { EntityInfo, EntityPartType, THREE } from '@amcax/web-render';
import { FieldProps } from '@rjsf/utils';
import { Flex, InputNumberProps, Slider } from 'antd';
import { ManagerId } from 'nextcae-base';
import { useEffect, useRef, useState } from 'react';
import {
  createPlane,
  findClosestPoint,
  getPlaneWorldNormal,
  handleSnapPoint,
  snapPoint,
} from '../../util';

/**
 * 通过选择单元上的两个点，确定一个轴；在加上一个方向绘制一个平面；
 * 可以连续选择点，每两个点确定一个平面
 */
export function TwoPointPlaneField({
  schema,
  uiSchema,
  disabled,
  onChange,
}: FieldProps) {
  const { workingMeshObject } = useDocumentStore();
  // 渲染、拾取相关的功能类
  const renderBase = useRef<RenderBase>(new RenderBase());
  // 记录吸附到线的端点，用于渲染辅助点和获取改点所能hover的单元
  const vertexPoint = useRef<THREE.Vector3Tuple>(null);
  // 保存拾取的entity，用于绘制辅助点、辅助平面等
  const [selectedEntities, setSelectedEntities] = useState<EntityInfo[]>([]);
  // 保存确定的平面法向，比拾取的entity少一个
  const planeNormals = useRef<THREE.Vector3Tuple[]>([]);
  // 辅助预览平面，使用户可以预览和网格的交线，该引用可用于释放
  const previewPlane = useRef<THREE.Mesh>(null);
  // 保存拾取了两个点之后，相机的方向；用于根据角度来计算平面的法向
  const normal = useRef<THREE.Vector3>(null);
  // 记录角度，用于计算平面的法向
  const [angle, setAngle] = useState(0);

  const layout = uiSchema?.['ui:layout'] ?? [1, 1];
  const fontSize = 12;

  useEffect(() => {
    widgetManager.hint('进入模型布线连线投影命令!');
    return () => {
      widgetManager.hint();
    };
  }, []);

  useEffect(() => {
    // 保存旧的拾取相关的信息，使得在组件销毁时可以还原
    const oldEnablePick = renderBase.current.enablePick;
    const oldPickFirstOnly = renderBase.current.pickFirstOnly;
    const oldEnableBoxSelect = renderBase.current.enableBoxSelect;
    const oldPickType = renderBase.current.pickType;

    // 开启拾取，不能框选，暂时只支持拾取单元（后续可通过schema来配置拾取类型）
    renderBase.current.enablePick = true;
    renderBase.current.pickFirstOnly = true; // 只拾取第一个，提高交互性能
    renderBase.current.enableBoxSelect = false;
    renderBase.current.pickType = [
      EntityPartType.line, // 支持拾取线，是为了吸附
      EntityPartType.element,
    ];

    // 添加相关监听事件，精确控制拾取和渲染数据
    renderBase.current.interactionMgr.addBeforeHovered(onBeforeHovered);
    renderBase.current.interactionMgr.addHoveredListener(onHovered);
    renderBase.current.interactionMgr.addClickListener(onClick);
    window.addEventListener('keydown', onKeydown);
    window.addEventListener('keyup', onKeyup);
    renderBase.current.container.addEventListener('wheel', onMouseWheel);
    return () => {
      // 重置保存的拾取信息
      renderBase.current.enablePick = oldEnablePick;
      renderBase.current.pickFirstOnly = oldPickFirstOnly;
      renderBase.current.enableBoxSelect = oldEnableBoxSelect;
      renderBase.current.pickType = oldPickType;
      // enabled controls，防止在按下ctrl键的时候关闭了命令，导致没有启用controls
      renderBase.current.controls.enabled = true;

      // 移除监听事件
      renderBase.current.interactionMgr.removeBeforeHovered(onBeforeHovered);
      renderBase.current.interactionMgr.removeHoveredListener(onHovered);
      renderBase.current.interactionMgr.removeClickListener(onClick);
      window.removeEventListener('keydown', onKeydown);
      window.removeEventListener('keyup', onKeyup);
      renderBase.current.container.removeEventListener('wheel', onMouseWheel);
      // 取消选中，移除组件渲染的点、线、面等
      renderBase.current.clearSelected();
      renderBase.current.assisMgr.group.remove(previewPlane.current);
      previewPlane.current = null;
      lineManager().clearLine();
      pointsManager().removePoints();
    };
  }, []);
  // 监听拾取的entity变化，绘制辅助点、辅助线
  useEffect(() => {
    // 绘制辅助线
    if (selectedEntities.length > 2) {
      // 只需要绘制前面的辅助线（最后一个点不需要绘制）
      const points = selectedEntities.slice(0, -1).map(({ point }) => point);
      lineManager().createUpdateLine(points);
    } else {
      lineManager().clearLine();
    }
    if (selectedEntities.length > 0) {
      // 获取selectedEntites最后两项，渲染辅助点
      const points = selectedEntities.slice(-2).map(({ point }) => point);
      pointsManager().renderPoints(points);
    } else {
      pointsManager().removePoints(PointEnum.TEMPP);
    }
  }, [selectedEntities]);
  // 监听角度和拾取的entity变化，重绘预览平面
  useEffect(() => {
    if (previewPlane.current) {
      // TODO 暂时通过移除再重新创建来模拟更新，待优化
      renderBase.current.assisMgr.group.remove(previewPlane.current);
    }
    // 只有拾取了两个及以上entity，才绘制辅助平面
    if (selectedEntities.length >= 2) {
      widgetManager.hint('按住CTRL键并滚动鼠标滚轮，可调整预览平面的角度!');
      // 获取selectedEntites最后两项，渲染辅助平面
      const points = selectedEntities.slice(-2).map(({ point }) => point);
      previewPlane.current = renderPreviewPlane(points, normal.current, angle);
      // 平面发生改变，则更新法向
      if (previewPlane.current) {
        // 之前已经加入法向了，所以这里更新最后一项
        planeNormals.current[planeNormals.current.length - 1] =
          getPlaneWorldNormal(previewPlane.current);
        onChange({
          entities: selectedEntities,
          normals: planeNormals.current,
        });
      }
    } else {
      widgetManager.hint('进入模型布线连线投影命令!');
      // 移除辅助平面
      renderBase.current.assisMgr.group.remove(previewPlane.current);
      previewPlane.current = null;
      // 数据已经不全了，直接通过onChange置null
      onChange(null);
    }
  }, [angle, selectedEntities]);
  /**
   * 在hover之前，判断如果hover的不是element，则能拾取；
   * 如果hover的是element，则只能拾取 workingMeshObject 网格的element
   */
  const onBeforeHovered = (e: EntityInfo) => {
    return e.type !== EntityPartType.element || e?.dxid === workingMeshObject;
  };
  /**
   * 在hover到entity后，绘制一个辅助点，该辅助点随鼠标移动，便于用户观察
   */
  const onHovered = (e?: EntityInfo) => {
    if (e?.point) {
      vertexPoint.current = snapPoint(e);
      // 吸附已拾取的entity的点
      if (!vertexPoint.current) {
        vertexPoint.current = findClosestPoint(
          e.point,
          selectedEntities.map(({ point }) => point),
        )?.point;
      }

      pointsManager().renderPoints(
        [vertexPoint.current ?? e.point],
        PointEnum.MOVEP,
      );
    } else {
      vertexPoint.current = null;
      // 在没有hover到entity时，移除辅助点
      pointsManager().removePoints(PointEnum.MOVEP);
    }
  };
  // 创建并渲染预览辅助平面
  const renderPreviewPlane = (
    points: THREE.Vector3Tuple[],
    normal: THREE.Vector3,
    angle: number,
  ) => {
    const meshMgr = renderBase.current.meshMgr;
    // 计算模型的包围盒，用来设置平面的大小
    const box = new THREE.Box3().setFromObject(meshMgr.group);

    const plane = createPlane(
      points,
      normal,
      box.getSize(new THREE.Vector3()),
      angle,
    );

    renderBase.current.assisMgr.group.add(plane);
    renderBase.current.render();
    return plane;
  };
  /**
   * 点击拾取后，在点击位置和entity交点也绘制一个固定的辅助点；
   * 并在拾取到两个entity后，绘制一个辅助平面，使得用户可以预览和网格的交线
   */
  const onClick = (e: EntityInfo) => {
    // 删除hover时的辅助点
    pointsManager().removePoints(PointEnum.MOVEP);

    if (vertexPoint.current) {
      e = handleSnapPoint(vertexPoint.current);
      nmAppDebugLog('根据吸附到的端点，拾取到单元的信息是：', e);
      vertexPoint.current = null;
      if (!e) {
        return;
      }
    }
    // 获取相机的方向，用来确定平面的法向，在这里获取是为了防止相机旋转导致法向变化
    normal.current = new THREE.Vector3();
    renderBase.current.camera.getWorldDirection(normal.current);

    selectedEntities.push(e);
    setSelectedEntities([...selectedEntities]);
    if (selectedEntities.length >= 2) {
      planeNormals.current.push(normal.current.toArray());
    }
  };
  /**
   * 监听键盘事件，用于判断是否禁用渲染的控制器
   */
  const onKeydown = (ev: KeyboardEvent) => {
    if (ev.ctrlKey) {
      // 按下ctrl键之后禁用控制器，让位旋转预览平面
      renderBase.current.controls.enabled = false;
    }
  };
  /**
   * 监听键盘事件，用于撤销最后一次操作
   */
  const onKeyup = (ev: KeyboardEvent) => {
    if (ev.ctrlKey === false) {
      // 松开ctrl键之后启用控制器
      renderBase.current.controls.enabled = true;
    }
    if (ev.ctrlKey && ev.key == 'z') {
      // ctrl+z 撤销
      // pop最后一个
      planeNormals.current.pop();
      const lastEntityInfo = selectedEntities.pop();
      setSelectedEntities([...selectedEntities]);
      // 并取消选中
      renderBase.current.setSelectOrHover(
        lastEntityInfo,
        false,
        EntOtpMod.SELECTED,
        ManagerId.Mesh,
      );
    }
  };
  /**
   * 监听鼠标滚轮事件，在按下ctrl键时，来调整预览平面角度
   */
  const onMouseWheel = (event: WheelEvent) => {
    if (event.ctrlKey && selectedEntities.length >= 2) {
      // 修改角度，调整预览平面的方向
      setAngle((oa) => {
        const delta = event.deltaY > 0 ? 5 : -5;
        // 使角度在0-180之间循环
        const newAngle = (oa + delta + 181) % 181; // +181 避免负数，181 是 0~180 共181个值
        return newAngle;
      });

      event.preventDefault();
    }
  };
  const onAngleChange: InputNumberProps['onChange'] = (value) => {
    const a = value as number;
    setAngle(a);
  };
  return (
    <>
      <Flex style={{ gap: 4, marginBottom: 4 }}>
        <Flex style={{ width: 'max-content', flex: layout[0] }}>
          <span style={{ flex: 1, fontSize }}>{schema.title}</span>
          {/* <PickIcon
          disabled={disabled}
          active={active}
          onClick={() => clickSetFocus()}
        /> */}
        </Flex>
        <Flex style={{ gap: '4px', flex: layout[1], fontSize }}>
          <>{'过两点平面'}</>
        </Flex>
      </Flex>
      <Flex style={{ gap: 4, marginBottom: 4 }}>
        <Flex
          style={{
            width: 'max-content',
            flex: layout[0],
            alignItems: 'center',
          }}>
          <span style={{ flex: 1, fontSize }}>角度</span>
        </Flex>
        <Flex style={{ gap: '4px', flex: layout[1], fontSize }}>
          <div style={{ flex: 1 }}>
            <Slider
              min={0}
              max={180}
              value={angle}
              disabled={disabled}
              onChange={onAngleChange}
            />
          </div>
        </Flex>
      </Flex>
    </>
  );
}
